#!/usr/bin/env python
# -*- coding: utf8 -*-

import os
import sys
import time
import socket
import libtorrent as lt
from functools import reduce
from optparse import OptionParser


state_str = [
    'queued', 'checking', 'downloading metadata',
    'downloading', 'finished', 'seeding',
    'allocating', 'checking fastresume'
]

def get_ips():
    addrs = socket.getaddrinfo(socket.gethostname(), None)
    return tuple(set([item[4][0] for item in addrs if ':' not in item[4][0]]))

def ip2long(ip):
    return reduce(lambda a, b: (a<<8)+b, [int(s) for s in ip.split('.')])

def get_wan_ips():
    wan_ips = []
    ips = get_ips()
    for ip in ips:
        ip_long = ip2long(ip)
        if ip2long('10.0.0.0') <= ip_long <= ip2long('10.255.255.255') \
            or ip2long('172.16.0.0') <= ip_long <= ip2long('172.31.255.255') \
            or ip2long('192.168.0.0') <= ip_long <= ip2long('192.168.255.255') \
            or ip2long('127.0.0.0') <= ip_long <= ip2long('127.255.255.255'):
            continue
        wan_ips.append(ip)
    return wan_ips

def remove_torrents(torrent, session):
    session.remove_torrent(torrent)

def add_suffix(val):
    prefix = ['B', 'kB', 'MB', 'GB', 'TB']
    for i in range(len(prefix)):
        if abs(val) < 1000:
            if i == 0:
                return '%5.3g%s' % (val, prefix[i])
            else:
                return '%4.3g%s' % (val, prefix[i])
        val /= 1000

    return '%6.3gPB' % val

def print_peer_info(peers):
    
    out = '         addr            down  ( total )   up   ( total )  q  r flags      progress    client\n'

    for peer in peers:
        out += '%-15s:%-5s ' % peer.ip
        out += '%s/s ' % add_suffix(peer.down_speed)
        out += '(%s) ' % add_suffix(peer.total_download)
        out += '%s/s ' % add_suffix(peer.up_speed)
        out += '(%s) ' % add_suffix(peer.total_upload)
        out += '%2d ' % peer.download_queue_length
        out += '%2d ' % peer.upload_queue_length

        out += 'I' if peer.flags & lt.peer_info.interesting else '.'
        out += 'C' if peer.flags & lt.peer_info.choked else '.'
        out += 'i' if peer.flags & lt.peer_info.remote_interested else '.'
        out += 'c' if peer.flags & lt.peer_info.remote_choked else '.'
        out += 'e' if peer.flags & lt.peer_info.supports_extensions else '.'
        out += 'l' if peer.flags & lt.peer_info.local_connection else 'r'
        out += ' '

        if peer.downloading_piece_index >= 0:
            assert(peer.downloading_progress <= peer.downloading_total)
            out +=  '%s(%s) ' % (peer.downloading_progress, peer.downloading_total)

        if peer.flags & lt.peer_info.handshake:
            id = 'waiting for handshake'
        elif peer.flags & lt.peer_info.connecting:
            id =  'connecting to peer'
        else:
            id = peer.client

        out += '%s\n' % id[:10]

    print(out)

def print_download_queue(download_queue):
    
    out = ""

    for e in download_queue:
        out += '%4d: [' % e['piece_index'];
        for b in e['blocks']:
            s = b['state']
            if s == 3:
                out += '#'
            elif s == 2:
                out += '='
            elif s == 1:
                out += '-'
            else:
                out += ' '
        out += ']\n'

    print(out)

def add_torrent(session, filename, options):
    atp = {}
    # 磁力链接
    if filename.startswith('magnet:'):
        atp = lt.parse_magnet_uti(filename)
    # torrent种子
    else:
        torrent = lt.bdecode(open(filename, 'rb').read())
        torrent_info = lt.torrent_info(torrent)
        atp['ti'] = torrent_info
        try:
            atp['resume_data'] = open(os.path.join(
                options.save_path, torrent_info.name() + '.fastresume'), 'rb').read()
        except:
            pass

    atp['save_path'] = options.save_path
    atp['storage_mode'] = lt.storage_mode_t.storage_mode_sparse
    atp['paused'] = False
    atp['auto_managed'] = True
    atp['duplicate_is_error'] = True
    session.async_add_torrent(atp)
    # handle = session.add_torrent(atp)

if __name__ == '__main__':
    usage = u'''
    usage: %prog [options] \n
        # 下载文件
        %prog <种子文件> -s <保存下载文件的地方 默认为'./'> -p <listening port> -u <限制上传速度 默认不限制> -d <限制下载速度 默认不限制> -e <做种时间(秒) 默认不限制> -r <http代理 xx.xx.xx.xx:port>\n      
    '''
    parser = OptionParser(usage=usage)

    parser.add_option('-s', '--save-path', type='string', dest='save_path', help='the path(folder) to save file', default='./')
    parser.add_option('-p', '--port', type='int', dest='port', help='set listening port', default=7000)
    parser.add_option('-u', '--max-upload-rate', type='float', dest='max_upload_rate', help='the maximum upload rate given in kB/s. -1 means infinite', default=-1)
    parser.add_option('-d', '--max-download-rate', type='float', dest='max_download_rate', help='the maximum download rate given in kB/s. -1 means infinite.', default=-1)
    parser.add_option('-e', '--seed_time', type='int', dest='seed_time', help='set sedd time, default unlimit', default=-1)
    parser.add_option('-r', '--proxy-host', type='string', dest='proxy_host', help='sets HTTP proxy host and port (separated by \':\')', default='')
    (options, args) = parser.parse_args()

    settings = {
        'user_agent': 'bt_client/' + lt.__version__,
        'alert_mask': lt.alert.category_t.all_categories
    }

    # 设置本地监听端口(默认 7000)
    if options.port < 0 or options.port > 65525:
        options.port = 7000
    settings['listen_interfaces'] = '0.0.0.0:%d' % options.port
    
    # 设置最大上传速度
    if options.max_upload_rate > 0:
        settings['upload_rate_limit'] = options.max_upload_rate * 1024
    
    # 设置最大下载速度
    if options.max_download_rate > 0:
        settings['download_rate_limit'] = options.max_download_rate * 1024

    # 设置 http 代理
    if options.proxy_host:
        settings['proxy_type'] = lt.proxy_type_t.http
        settings['proxy_hostname'] = options.proxy_host.split(':')[0]
        settings['proxy_port'] = options.proxy_host.split(':')[1]
    
    session = lt.session()
    sts = lt.session_settings()
    for k, v in settings.items():
        setattr(sts, k, v)

    wan_ips = get_wan_ips()
    if wan_ips:
        print('使用外网IP: {}'.format(wan_ips[0]))
        session.listen_on(6881, 6881, wan_ips[0])
    else:
        session.listen_on(6881, 6881)

    # 添加 磁力链接/torrent
    for filename in args:
        add_torrent(session, filename, options)

    torrents = {}
    times = 0
    while True:
        alerts = session.pop_alerts()
        # sys.stdout.write('\033[2J\033[0;0H')

        for alert in alerts:
            # 添加了新的torrent
            if type(alert) == lt.add_torrent_alert:
                handle = alert.handle
                # 参考 https://libtorrent.org/reference-Core.html#torrent_handle
                handle.set_max_connections(60)
                handle.set_max_uploads(-1)
                torrents[handle.name()] = handle

                import pdb ; pdb.set_trace()

            # elif type(alert) == lt.state_update_alert:
            #     handle = alert.handle
            #     for s in alert.status:
            #         torrents[handle] = s

            print(alert.message())

        out = ''

        for name, handle in torrents.items():
            status = handle.status()
            out += '%s (%6s) ' % (status.name[:40], status.active_time)
            out += state_str[status.state] + ': %5.4f%% ' % (status.progress * 100)

            if status.state != lt.torrent_status.seeding:
                out += 'total_done: %s ' % add_suffix(status.total_done)
                out += 'peers: %d seeds: %d distributed_copies: %d ' % \
                    (status.num_peers, status.num_seeds, status.distributed_copies)

            out += 'download: %s/s (%s) ' \
                % (add_suffix(status.download_rate), add_suffix(status.total_download))
            out += 'upload: %s/s (%s) ' \
                % (add_suffix(status.upload_rate), add_suffix(status.total_upload))

            # if status.state != lt.torrent_status.seeding:
            #     out += 'info-hash: %s\n' % status.info_hash
            #     out += 'next announce: %s\n' % status.next_announce
            #     out += 'tracker: %s\n' % status.current_tracker

            print(out)

            times += 1
            if times % 5 == 0:
                print_peer_info(handle.get_peer_info())
                # print_download_queue(handle.get_download_queue())

            # if status.state != lt.torrent_status.seeding:
            #     try:
            #         out = '\n'
            #         fp = handle.file_progress()
            #         ti = status.torrent_file
            #         for f, p in zip(ti.files(), fp):
            #             out += (p / float(f.size))
            #             out += ' ' + f.path + '\n'
            #         print(out)
            #     except:
            #         pass


        time.sleep(1)